/*
 * chrome://tabtreemix/content/links/contentLinks.js
 *
 * original code by Bradley Chapman
 * modified and developped by Hemiola SUN
 * modified again by Bradley Chapman
 *
 */
var gIOService =     Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
var gPref =          Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
var gWindowManager = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);

/**
 * @brief Open the given link node in the current window.
 *
 * @param event		A valid event union.
 * @param linkNode	The DOM node containing the URL to open.
 * @param currentTab	A Boolean value. If true, the URL will be opened
 *			within the current tab. If false, it will be
 *			opened in a new tab.
 * @returns		True if the function opened a URL, or the value
 *			of handleLinkClick() if it chose not to.
 *
 */
function TMP_howToOpen(event, linkNode, currentTab)
{
  // this helper function parses the event union for us
  // and makes a better determination of how a link will
  // be opened
  var where = whereToOpenLink(event);

  if (where == "save" || where == "window" )
     return handleLinkClick(event, linkNode.href, linkNode);

  if (!webPanelSecurityCheck(linkNode.ownerDocument.location.href, linkNode.href))
    return false;

  var postData = { };
  var url = getShortcutOrURI(linkNode.href, postData);
  if (!url)
    return true;

  // if where is "tab", "tabshifted", "current" callers will control the inversion of currentTab
  var docURL = event.target.ownerDocument.location.href; // referrer header

  if (currentTab) {
    // makeURI in FF 1.5+ , makeURL in FF 1.0.x
    var referrerURI = ("makeURI" in window) ? makeURI(docURL) : makeURL(docURL);
    window.loadURI(url, referrerURI, null, false);
  } else {
    try{
      var f = window.openNewTabWith.toString();
      if (f.indexOf("event") > f.indexOf("postData"))
         window.openNewTabWith(url, docURL, null, event, false); // firefox 1.5 +
      else
         window.openNewTabWith(url, linkNode, event, true, null); // firefox 1.0.x
    } catch (e) {}
  }

  event.preventDefault();
  event.stopPropagation();

  return true;
}

/**
 * @brief Check for certain JavaScript strings inside an attribute.
 *
 * @param attr			The attribute to check.
 * @param string		The string to check for.
 * @returns			true if the strings are present, false if they aren't.
 *
 */
function TMP_checkAttr(attr, string)
{
   if (typeof(attr) == "string") return attr.indexOf(string) == 0;
   return false;
}

/**
 * @brief Check if link refers to external domain.
 *
 * @param target  The target link.
 * @param curpage  The current page url
 * @returns       current domain and target domain
 *
 */
function checkDomain(curpage, target)
{
  function stipDomain(url) {
    if (url.match(/^file:/))
      return "local_file";
    else if (url.match(/^http:\/\/localhost/))
      url = url.replace("http://localhost:", "http://localhost.");
    
    if (url.match(/^http/)) {
      var re = new RegExp('([^.]*.([^/]*))');
      return re.exec(url)[2];
    }
    return null;
  }

  return {current: stipDomain(curpage.toString()), target: stipDomain(target.toString())}
}

/**
 * @brief Handle left-clicks inside a browser viewport.
 *
 * This function is the primary entry point for all left-clicks on a browser
 * page; we triage and sort such clicks and handle the ones we want and pass
 * on the ones that we don't.
 *
 * @param event			A valid event union.
 * @param fieldNormalClicks	A Boolean value. If true, we will handle all left-clicks
 *				that invoke this function. If false, we will only handle
 *				the ones that require additional legwork (i.e. locked tabs).
 * @returns			Either the return value of __contentAreaClick(), or the
 *				return value of handleLinkClick(), or true if the function
 *				was passed an event it could not handle.
 */
function TMP_contentAreaClick(event, fieldNormalClicks)
{
  try {
    var targetPref = gPref.getIntPref("extensions.tabmix.speLink");
    var linkTarget = gPref.getBoolPref("extensions.tabmix.linkTarget");
    var suppressTabs = gPref.getBoolPref("extensions.tabmix.enablefiletype")
  }
  catch(e) {}
  // use the default if either of these are activated
  if (!event.isTrusted || event.getPreventDefault()) {
     return true;
  }
  var target = event.target;
  var linkNode;
  var where = whereToOpenLink(event);

  if (target instanceof HTMLButtonElement ||
      target instanceof HTMLInputElement) {
    if (typeof(SubmitToTab)!='undefined' && SubmitToTab.contentAreaClick(event) == false) {
      return false;
    }
  }

  if (target instanceof HTMLAnchorElement ||
      target instanceof HTMLAreaElement ||
      target instanceof HTMLLinkElement) {
    if (target.hasAttribute("href"))
      linkNode = target;

     // xxxmpc: this is kind of a hack to work around a Gecko bug (see bug 266932)
     // we're going to walk up the DOM looking for a parent link node,
     // this shouldn't be necessary, but we're matching the existing behaviour for left click
     var parent = target.parentNode;
     while (parent) {
       if (parent instanceof HTMLAnchorElement ||
           parent instanceof HTMLAreaElement ||
           parent instanceof HTMLLinkElement) {
           if (parent.hasAttribute("href"))
             linkNode = parent;
       }
       parent = parent.parentNode;
     }
  }
  else {
    linkNode = event.originalTarget;
    while (linkNode && !(linkNode instanceof HTMLAnchorElement))
      linkNode = linkNode.parentNode;
    // <a> cannot be nested.  So if we find an anchor without an
    // href, there is no useful <a> around the target
    if (linkNode && !linkNode.hasAttribute("href"))
      linkNode = null;
    else if (linkNode && linkNode.hasAttribute("href"))
      target = linkNode;
  }

  if (!linkNode)
    return __contentAreaClick(event, fieldNormalClicks);

  // Check if new tab already opened from onclick event // 2006-09-26
  if (target.hasAttribute("onclick") && gBrowser.contentDocument.location.href != document.commandDispatcher.focusedWindow.top.location.href)
    return true;

  // Check if link refers to external domain.
  // Get current page url
  var curpage = linkNode.ownerDocument.location.href; // 2006-09-29
  var domain = checkDomain(curpage, target);
  var targetDomain = domain.target;
  var currentDomain = domain.current;

  var openT = linkNode.getAttribute("target");
  // If link has no target attribute, check if there is a <base> with a target attribute
  if (!openT) {
    var b = document.commandDispatcher.focusedWindow.document.getElementsByTagName("base");
    if (b.length > 0)
      openT = b[0].getAttribute("target");
  }

  /*
   * prevent tabs from opening if left-clicked link ends with given filetype or matches regexp;
   * portions were taken from disable target for downloads by cusser
   *
   */
  if (TMP_suppressTabsOnFileDownload(event, target, linkNode, suppressTabs)) {
    return true;
  }

  /*
   * force a middle-clicked link to open in the current tab if certain conditions
   * are true. See the function comment for more details.
   *
   */
  if (TMP_divertMiddleClick(event, linkNode, gBrowser.mCurrentTab, currentDomain, targetDomain,
                            targetPref, gPref.getBoolPref("extensions.tabmix.middlecurrent"))) {
    return true;
  }

  // catch other middle & right click
  if (event.button != 0) {
    handleLinkClick(event, linkNode.href, linkNode);
    return true;
  }

  // the rest of the code if for left-click only

  /*
   * open targeted links in the current tab only if certain conditions are met.
   * See the function comment for more details.
   *
   */
  if (TMP_divertTargetedLink(event, target, linkNode, openT,
                             document.commandDispatcher.focusedWindow.top.frames,
                             gBrowser.mCurrentTab, currentDomain, targetDomain,
                             targetPref, linkTarget)) {
    return true;
  }

  /*
   * open links to other sites in a tab only if certain conditions are met. See the
   * function comment for more details.
   *
   */
  if (TMP_openExSiteLink(event, target, linkNode, currentDomain, targetDomain, targetPref)) {
    return true;
  }

  if (gBrowser.mCurrentTab.hasAttribute("locked")) { // tab is locked
    var href = null, onclick = null;
    if (target.hasAttribute("href")) href = target.getAttribute("href").toLowerCase();
    if (target.hasAttribute("onclick")) onclick = target.getAttribute("onclick");
    if (TMP_checkAttr(href, "javascript:") ||
        TMP_checkAttr(href, "data:") ||
        TMP_checkAttr(onclick, "window.open") ||
        TMP_checkAttr(onclick, "NewWindow") ||
        TMP_checkAttr(onclick, "PopUpWin") ||
        (onclick && onclick.indexOf('this.target="_Blank"') != -1) ||
        TMP_checkAttr(onclick, "return ")) {
      ; // javascript links, do nothing!
    }
    else {
      var current = TMP_checkAttr(href, "#");
      TMP_howToOpen(event, linkNode, current);
    }
  }
  // use whereToOpenLink() to determine if no modifiers were used
  else if (where == "current") {
      if (fieldNormalClicks && (!openT || openT == "_content" || openT  == "_main"))
        __contentAreaClick(event, fieldNormalClicks);
      else if (linkNode.getAttribute("rel") == "sidebar")
        __contentAreaClick(event, fieldNormalClicks);
      else if (openT == "_search")
        __contentAreaClick(event, fieldNormalClicks);
      else if (linkNode.hasAttribute("onclick"))
        __contentAreaClick(event, fieldNormalClicks);

      //disable tab mix completely for mailto: links
      else if (linkNode.getAttribute("href").indexOf("mailto:") > -1)
        __contentAreaClick(event, fieldNormalClicks);
      else if (openT)
        handleLinkClick(event, linkNode.href, linkNode);
    }
  else
      handleLinkClick(event, linkNode.href, linkNode);

  return true;
}

/**
 * @brief Suppress tabs that may be created by downloading a file.
 *
 * This code borrows from Cusser's Disable Targets for Downloads extension.
 *
 * @param event         A valid event union.
 * @param target        The target of the event.
 * @param linkNode      The DOM node containing the URL to be opened.
 * @param suppressTabs  A Boolean value that controls controlling how the link should be opened.
 * @returns             true if the link was handled by this function.
 *
 */
function TMP_suppressTabsOnFileDownload(event, target, linkNode, suppressTabs)
{
   // prevent link with "custombutton" protocol to open new tab when custombutton extension exist
   if (typeof(custombuttons) !='undefined'){
      if (TMP_checkAttr(linkNode.toString(), "custombutton://"))
         return true;
   }

   if (event.button != 0 || event.ctrlKey || event.metaKey || !suppressTabs) 
      return false;

   // lets try not to look into links that start with javascript (from 2006-09-02)
   if (TMP_checkAttr(linkNode.toString(), "javascript:"))
      return false;

   if (target.hasAttribute("onclick")) {
      var onclick = target.getAttribute("onclick");
      if (TMP_checkAttr(onclick, "return install") ||
          TMP_checkAttr(onclick, "return installTheme") ||
          TMP_checkAttr(onclick, "return note") || TMP_checkAttr(onclick, "return log")) // click on link in http://tinderbox.mozilla.org/showbuilds.cgi
         return true;
   }

   // prevent links in tinderbox.mozilla.org with linkHref to *.gz from open in this function
   if (TMP_checkAttr(linkNode.toString() , "http://tinderbox.mozilla.org/showlog") ||
      TMP_checkAttr(linkNode.toString() , "http://tinderbox.mozilla.org/addnote")) return false;

   var filetype = gPref.getCharPref("extensions.tabmix.filetype");
   filetype = filetype.toLowerCase();
   filetype = filetype.split(" ");

   var linkHrefExt = "", linkHref = target.getAttribute("href");
   if (linkHref) {
      linkHref = linkHref.toLowerCase();
      linkHrefExt = linkHref.substring(linkHref.lastIndexOf("/"),linkHref.length);
      linkHrefExt = linkHrefExt.substring(linkHrefExt.indexOf("."),linkHrefExt.length);
   }

   var testString, hrefExt, testExt;
   for (var l = 0; l < filetype.length; l++) {

     if (filetype[l].indexOf("/") != -1){
       testString = filetype[l].substring(1,filetype[l].length-1);
       hrefExt = linkHref;
     }
     else {
       testString = "\\." + filetype[l];
       hrefExt = linkHrefExt;

       // prevent filetype catch if it is in the middle of a word
       testExt = new RegExp(testString + "[a-z0-9?\.]+", 'i');
       if (testExt.test(hrefExt))
         continue;
     }
     testExt = new RegExp(testString, 'i');

     if (testExt.test(hrefExt)) {
       event.preventDefault();
       TMP_howToOpen(event, linkNode, true);
       return true;
     }
   }
   return false;
}

/**
 * @brief Divert middle-clicked links into the current tab.
 *
 * This function forces a middle-clicked link to open in the current tab if
 * the following conditions are true:
 *
 * - links to other sites are not configured to open in new tabs AND the current
 *   page domain and the target page domain do not match OR the current
 *   tab is locked
 * - middle-clicks are configured to open in the current tab AND the middle
 *   mouse button was pressed OR the left mouse button and one of the Ctrl/Meta keys
 *   was pressed
 *
 * @param event			A valid event union.
 * @param linkNode		The DOM node containing the URL to open.
 * @param currentTab		A scripted tab object from the tabbrowser.
 * @param currentDomain		The domain name of the website URL in the current tab.
 * @param targetDomain		The domain name of the website URL in the link node.
 * @param targetPref		An integer value that specifies whether or not links should
 *				be forced into new tabs.
 * @param middlePref		A Boolean value that controls how middle clicks are handled.
 * @returns			true if the function handled the click, false if it didn't.
 *
 */
function TMP_divertMiddleClick(event, linkNode, currentTab, currentDomain, targetDomain,
                               targetPref, middlePref)
{
   if (!middlePref) 
      return false;

   var isTabLocked = currentTab.hasAttribute("locked");
   var isDifDomain = targetPref == 2 && targetDomain &&
                     targetDomain != currentDomain;
   if (!isTabLocked && !isDifDomain)
      return false;

   if (event.button == 1 || event.button == 0 && (event.ctrlKey || event.metaKey)) {
     event.preventDefault();
     TMP_howToOpen(event, linkNode, true);
     return true;
   }
   return false;
}

/**
 * @brief Divert links that contain targets to the current tab.
 *
 * This function forces a link with a target attribute to open in the
 * current tab if the following conditions are true:
 *
 * - linkTarget is set
 * - neither of the Ctrl/Meta keys were used AND the linkNode has a target attribute
 *   AND the content of the target attribute is not one of the special frame targets
 *   AND it is not present in the document frame pool
 * - links to other sites are not configured to open in new tabs AND the domain name
 *   of the current page and the domain name of the target page do not match
 * - the current tab is not locked
 * - the  domain name of the current page and the domain name of the target page
 *   do not match
 * - the target of the event has an onclick attribute that does not contain the
 *   function call 'window.open' or the function call 'return top.js.OpenExtLink'
 *
 * @param event            A valid event union.
 * @param target           The target of the event.
 * @param linkNode         The DOM node containing the URL to be opened.
 * @param targetAttr       The target attribute of the link node.
 * @param frames           The frame pool of the current document.
 * @param currentTab       A scripted tab object from the tabbrowser.
 * @param currentDomain    The domain name of the website URL loaded in the current tab.
 * @param targetDomain     The domain name of the website URL to be loaded.
 * @param targetPref       An integer value that specifies whether or not links should
 *                         be forced into new tabs.
 * @param linkTarget       An integer value that specifies how normal links
 *                         that spawn new windows are handled.
 * @returns                true if the function handled the click, false if it didn't.
 *
 */
function TMP_divertTargetedLink(event, target, linkNode, targetAttr, frames,
                                currentTab, currentDomain, targetDomain,
                                targetPref, linkTarget)
{
  if (!linkTarget) return false;
  if (TMP_checkAttr(linkNode.toString(), "javascript:") || // 2005-11-28 some link in Bloglines start with javascript
      TMP_checkAttr(linkNode.toString(), "data:")) 
    return false;

  if (event.ctrlKey || event.metaKey) return false;

  if (!targetAttr) return false;
  var targetString = /^(_self|_parent|_top|_content|_main)$/;
  if (targetString.test(targetAttr.toLowerCase())) return false;

  if (TMP_existsFrameName(frames, targetAttr)) return false;

  if (targetPref == 2 && targetDomain && targetDomain != currentDomain) return false;
  if (currentTab.hasAttribute("locked")) return false;
  if (targetDomain && targetDomain == currentDomain) return false;

  if (target.hasAttribute("onclick")) {
    var onclick = target.getAttribute("onclick");
    if (TMP_checkAttr(onclick, "window.open") ||
        TMP_checkAttr(onclick, "NewWindow") ||
        TMP_checkAttr(onclick, "PopUpWin") ||
        TMP_checkAttr(onclick, "return "))
          return false;
  }
  TMP_howToOpen(event, linkNode, true);
  return true;
}

/**
 * @brief Open links to other sites in tabs as directed.
 *
 * This function opens links to external sites in tabs as long as the following
 * conditions are met:
 *
 * - links to other sites are configured to open in tabs
 * - the link node does not have an 'onclick' attribute that contains either the function call
 *   'window.open' or the function call 'return top.js.OpenExtLink'.
 * - the domain name of the current page and the domain name of the target page do not match
 *   OR the link node has an 'onmousedown' attribute that contains the text 'return rwt'
 *
 * @param event             A valid event union.
 * @param target           The target of the event.
 * @param linkNode         The DOM node containing the URL to be opened.
 * @param currentDomain    The domain name of the website URL loaded in the current tab.
 * @param targetDomain     The domain name of the website URL to be loaded.
 * @param targetPref       An integer value that specifies whether or not links should
 *                         be forced into new tabs.
 * @returns                true if the function handled the click, false if it didn't.
 *
 */
function TMP_openExSiteLink(event, target, linkNode, currentDomain, targetDomain, targetPref)
{
  if (targetPref != 2) return false;

  if (target.hasAttribute("onclick")) {
    var onclick = target.getAttribute("onclick");
    if (TMP_checkAttr(onclick, "window.open") ||
        TMP_checkAttr(onclick, "NewWindow") ||
        TMP_checkAttr(onclick, "PopUpWin") ||
        TMP_checkAttr(onclick, "return "))
            return false;
  }
  if (targetDomain && targetDomain != currentDomain ||
     TMP_checkAttr(target.getAttribute("onmousedown"), "return rwt")) {
    TMP_howToOpen(event, linkNode, false);
    return true;
  }
  return false;
}

/**
 * @brief Check a document's frame pool and determine if
 * |targetFrame| is located inside of it.
 *
 * @param containerFrame	The frame pool of the current document.
 * @param targetFrame		The name of the frame that we are seeking.
 * @returns			true if the frame exists within the given frame pool,
 *				false if it does not.
 */
function TMP_existsFrameName(containerFrame, targetFrame)
{
    for (var i = 0; i < containerFrame.length; ++i) {
          if (containerFrame[i].name == targetFrame) return true;
          if (containerFrame[i].frames.length) var return_var = TMP_existsFrameName(containerFrame[i].frames,targetFrame);
    }

    if (return_var) return return_var;
    return false;
}

/**
 * @brief Handle left-clicks on links within a help browser
 *
 * NOTE: Shift, Ctrl, and Meta are all ignored
 *
 * @param event		A valid event union.
 * @return		true if the event was not handled;
 *			false if it was handled and a URI was
 *			loaded.
 *
 */
function TMP_helpContentClick(event)
{
  var helpBrowser = document.getElementById("help-content");
  if (!helpBrowser) {
    return true;
  }

  if (event.button != 0) return true;
  // shall we ignore this?
  if (is_ignorable(event.target) || is_ignorable(event.originalTarget)) {
    return true;
  }

  var linkNode;
  var target = event.target;

  if (target instanceof HTMLAnchorElement ||
      target instanceof HTMLAreaElement ||
      target instanceof HTMLLinkElement) {
    if (target.hasAttribute("href")) {
      linkNode = target;
    }
  }
  else {
    linkNode = event.originalTarget;
    while (linkNode && !(linkNode instanceof HTMLAnchorElement)) {
      linkNode = linkNode.parentNode;
    }
    // <a> cannot be nested.  So if we find an anchor without an
    // href, there is no useful <a> around the target
    if (linkNode && !linkNode.hasAttribute("href")) {
      return true;
    }
  }
  var uriSpec;
  if (linkNode instanceof Node && linkNode.hasAttribute("href")) {
    uriSpec = linkNode.href;
  }
  if (!uriSpec || uriSpec == undefined) {
    return true;
  }

  // do not handle URIs that are not http, ftp or https
  var URI = gIOService.newURI(uriSpec, null, null);
  if (!URI.schemeIs("http") && !URI.schemeIs("ftp") && !URI.schemeIs("https")) {
    helpBrowser.loadURI(URI.spec);
    return false;
  }

  TMP_openURL(URI.spec, event);
  return false;
}

/**
 * Determine if a node should be ignored by the iterator functions.
 *
 * @param nod  An object implementing the DOM1 |Node| interface.
 * @return     true if the node is:
 *                1) A |Text| node that is all whitespace
 *                2) A |Comment| node
 *             and otherwise false.
 */
function is_ignorable(nod)
{
   return  (nod.nodeType == 8) || // A comment node
      ((nod.nodeType == 3) && is_all_ws(nod)); // a text node, all ws
}

/**
 * @brief Locate a browser window.
 *
 * @param aExclude	A scripted window object that we do not
 *			want to use.
 * @returns		A scripted window object representing a browser
 *			window that is not the same as aExclude, and is
 *			additionally not a popup window.
 *
 */
function TMP_getBrowserWindow(aExclude)
{
    var windows = gWindowManager.getEnumerator('navigator:browser');

    while (windows.hasMoreElements()) {
        var win = windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow);
        if (TMP_checkForPopup(win.QueryInterface(Components.interfaces.nsIDOMWindowInternal)))
            continue;

        // this returns the first window that we find; it is not exhaustive
        if (win != aExclude) return win;
    }
    return null;
}

/**
 * @brief Checks to see if a given nsIDOMWindowInternal window is a popup or not.
 *
 * @param domWindow	   A scripted nsIDOMWindowInternal object.
 * @return		   true if the domWindow is a popup, false otherwise.
 *
 */
function TMP_checkForPopup(domWindow)
{
  if (!(domWindow instanceof Components.interfaces.nsIDOMWindowInternal)) return false;

  // FIXME: locationbar, menubar, toolbar -
  // if these are hidden the window is probably a popup
  var locbar =  domWindow.locationbar.QueryInterface(Components.interfaces.nsIDOMBarProp);
  var menubar = domWindow.menubar.QueryInterface(Components.interfaces.nsIDOMBarProp);
  var toolbar = domWindow.toolbar.QueryInterface(Components.interfaces.nsIDOMBarProp);

  // the following logic, while possibly slow, is designed
  // to catch all reasonable permutations of hidden UI
  if ((!locbar.visible && !menubar.visible && !toolbar.visible) ||
      (!locbar.visible && !menubar.visible) ||
      (!menubar.visible && !toolbar.visible)) {
    return true;
  }
  return false;
}

/*
 * handle all DOM window open events and catch attempts to open new windows
 *
 * PRECONDITION: None.
 * POSTCONDITION: None.
 *
 */
var TMP_DOMWindowOpenObserver = {
    Tbplite: 'tbpl',

    observe : function(aSubject, aTopic, aData)
    {
        if (aTopic != 'domwindowopened') return;
        this.onObserve(aSubject, this);
        return;
    },

    onObserve : function(aSubject, aThis)
    {
        gSingleWindowMode = TMP_getBoolPref(tabxBranch, "singleWindow", false);
        tabxPrefObserver.setLink_openPrefs();
        if (!gSingleWindowMode)
          return;

        var newWindow = aSubject;
        var existingWindow = TMP_getBrowserWindow(newWindow);

        // no navigator:browser window open yet?
        if (!existingWindow) return;

        // if the href is missing, try again later (xxx)
        if (!newWindow.location.href) {
            existingWindow.setTimeout(aThis.onObserve, 0, newWindow, aThis);
            return;
        }

        // we don't want to open non-browser windows in a tab
        if(newWindow.location.href != "chrome://browser/content/browser.xul")
            return;

        if ( !('arguments' in newWindow) || newWindow.arguments.length == 0 )
          return;

        var _Browser = existingWindow.getBrowser();
        existingWindow.tablib.init(); // just incase tablib isn't init yet
        var newTab = _Browser.addTab(newWindow.arguments[0], null, null);
        _Browser.TMP_selectNewForegroundTab(newTab, false, newWindow.arguments[0]);

        setTimeout(newWindow.close, 0);
        return;
    }
}
// end of TMP_DOMWindowOpenObserver
